#include <assert.h>
#include <sel4/sel4.h>
#include <stdlib.h>

#include "list.h"

static void _insert_after(list l, list_node *dst, list_node *new) {
    new->prev = dst;
    new->next = dst->next;
    if (dst->next == NULL) {
        l->tail = new;
    } else {
        dst->next->prev = new;
    }
    dst->next = new;
}

static void _insert_before(list l, list_node *dst, list_node *new) {
    new->prev = dst->prev;
    new->next = dst;
    if (dst->prev == NULL) {
        l->head = new;
    } else {
        dst->prev->next = new;
    }
    dst->prev = new;
}

static inline list_node *_make_node(SOS_LIST_TYPE val) {
    list_node *ret = malloc(sizeof(struct list_node));
    ret->value = val;
    return ret;
}

static inline void _remove_node(list l, list_node *node) {
    // Special handling when node == head
    if (node->prev == NULL) {
        l->head = node->next;
    } else {
        node->prev->next = node->next;
    }
    // Special handling when node == tail
    if (node->next == NULL) {
        l->tail = node->prev;
    } else {
        node->next->prev = node->prev;
    }
    l->size--;
    free(node);
}

list list_init(void) {
    list ret = malloc(sizeof(struct list));
    ret->head = NULL;
    ret->tail = NULL;
    ret->size = 0;
    return ret;
}

void list_destroy(list l) {
    list_node *ptr = l->head;
    while (ptr != NULL) {
        list_node *tmp = ptr->next;
        free(ptr);
        ptr = tmp;
    }
    free(l);
}

int list_member(list l, SOS_LIST_TYPE val) {
    list_node *cur = l->head;

    while (cur != NULL) {
        if (cur->value == val) return 1;
        cur = cur->next;
    }

    return 0;
}

int list_push_front(list l, SOS_LIST_TYPE val) {
    list_node *new = _make_node(val);
    if (new == NULL) return SOS_LIST_OOM;
    l->size++;
    if (l->head == NULL) {
        l->head = new;
        l->tail = new;
        new->next = NULL;
        new->prev = NULL;
        return SOS_LIST_SUCCESS;
    } else {
        _insert_before(l, l->head, new);
        return SOS_LIST_SUCCESS;
    }
}

int list_push_back(list l, SOS_LIST_TYPE val) {
    list_node *new = _make_node(val);
    if (new == NULL) return SOS_LIST_OOM;
    if (l->tail == NULL) {
        return list_push_front(l, val);
    } else {
        l->size++;
        _insert_after(l, l->tail, new);
        return SOS_LIST_SUCCESS;
    }
}

SOS_LIST_TYPE list_pop_front(list l) {
    assert(l->head);
    SOS_LIST_TYPE ret = l->head->value;
    _remove_node(l, l->head);
    return ret;
}

SOS_LIST_TYPE list_pop_back(list l) {
    assert(l->head);
    SOS_LIST_TYPE ret = l->tail->value;
    _remove_node(l, l->tail);
    return ret;
}

void list_map_left(list l, int (*fun)(SOS_LIST_TYPE*)) {
    int delete;
    list_node *ptr = l->head;
    list_node *tmp;
    while (ptr != NULL) {
        delete = (*fun)(&(ptr->value));
        tmp = ptr->next;
        if (delete) _remove_node(l, ptr);
        ptr = tmp;
    }
}

void list_map_right(list l, int (*fun)(SOS_LIST_TYPE*)) {
    int delete;
    list_node *ptr = l->tail;
    list_node *tmp;
    while (ptr != NULL) {
        delete = (*fun)(&(ptr->value));
        tmp = ptr->prev;
        if (delete) _remove_node(l, ptr);
        ptr = tmp;
    }
}

int list_remove_left(list l, SOS_LIST_TYPE val) {
    list_node *ptr = l->head;
    while (ptr != NULL) {
        if (ptr->value == val) {
            _remove_node(l, ptr);
            return SOS_LIST_SUCCESS;
        }
        ptr = ptr->next;
    }
    return SOS_LIST_NOENT;
}

int list_remove_right(list l, SOS_LIST_TYPE val) {
    list_node *ptr = l->tail;
    while (ptr != NULL) {
        if (ptr->value == val) {
            _remove_node(l, ptr);
            return SOS_LIST_SUCCESS;
        }
        ptr = ptr->prev;
    }
    return SOS_LIST_NOENT;
}
